package com.parcial.data.fachadas;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

import com.parcial.data.conn.Conexion;
import com.parcial.data.entidades.Entidad;
import com.parcial.data.util.Campo;
import com.parcial.data.util.Clave;

public abstract class FachadaAbstracta<T extends Entidad> {

	protected Conexion conn;
	protected ResultSet rs;
	protected PreparedStatement stmt;

	public FachadaAbstracta() {
		conn = Conexion.obtenerConexion();
	}

	protected abstract List<T> convertirRS(ResultSet rs);

	protected abstract String obtenerNombreTabla();

	protected abstract String obtenerOrderString();

	protected abstract List<Campo> obtenerCampos(T entidad);

	private String obtenerInsertStatement(T entidad) {
		StringBuilder sb = new StringBuilder("INSERT INTO ").append(
				obtenerNombreTabla()).append(" VALUES (");
		for (Campo campo : obtenerCampos(entidad)) {
			Object obj = campo.getValue();
			if (obj == null) {
				sb.append("null ,");
			} else {
				sb.append("? ,");
			}

		}
		sb.deleteCharAt(sb.length() - 1);
		return sb.append(")").toString();
	}

	private String obtenerUpdateStatement(T entidad) {
		StringBuilder sb = new StringBuilder("UPDATE ").append(
				obtenerNombreTabla()).append(" SET ");
		for (Field f : entidad.getClass().getDeclaredFields()) {
			if(f.getName() != "serialVersionUID") {
				sb.append(f.getName()).append(" = ? ,");
			}
		}
		sb.deleteCharAt(sb.length() - 1);
		sb.append(" WHERE ");
		for (String s : entidad.getClave().getColumnas()) {
			sb.append(s).append(" = ? AND ");
		}
		return sb.delete(sb.length() - 4, sb.length() - 1).toString();
	}

	public List<T> obtenerTodos() {
		List<T> resultado = new ArrayList<T>();
		StringBuilder sb = new StringBuilder("SELECT * FROM ")
				.append(obtenerNombreTabla()).append(" ORDER BY ")
				.append(obtenerOrderString());
		
		String sql = sb.toString();
		
		try {
			stmt = conn.prepareStatement(sql);
			rs = stmt.executeQuery();
			resultado = convertirRS(rs);
		} catch (Exception e) {
			System.out.println("No se pudo realizar la consulta de "
					+ obtenerNombreTabla() + " con la consulta");
			e.getMessage();
			e.printStackTrace();
		}
		return resultado;
	}
	
	public List<T> obtenerListaPorCampos(Clave campos) {		
		List<T> resultado = new ArrayList<T>();
		StringBuilder sb = new StringBuilder("SELECT * FROM ").append(
				obtenerNombreTabla()).append(" WHERE ");
		for (String nombreCol : campos.getColumnas()) {
			sb.append(nombreCol).append(" = ? AND ");
		}
		sb.delete(sb.length() - 4, sb.length() - 1);
		String sql = sb.toString();
		try {
			stmt = conn.prepareStatement(sql);
			for (int i = 1; i <= campos.getValores().length; i++) {
				Object valorCol = campos.getValores()[i - 1];
				stmt.setObject(i, valorCol);
			}
			rs = stmt.executeQuery();
			resultado = convertirRS(rs);
		} catch (Exception e) {
			System.out.println("No se pudo realizar la consulta de "
					+ obtenerNombreTabla() + " con la consulta " + sql);
			e.getMessage();
			e.printStackTrace();
		}
		return resultado;
	}

	public T obtenerPorClave(Clave clave) {
		List<T> resultado = new ArrayList<T>();
		T res = null;
		StringBuilder sb = new StringBuilder("SELECT * FROM ").append(
				obtenerNombreTabla()).append(" WHERE ");
		for (String nombreCol : clave.getColumnas()) {
			sb.append(nombreCol).append(" = ? AND ");
		}
		sb.delete(sb.length() - 4, sb.length() - 1);
		
		String sql = sb.toString();
		
		try {
			stmt = conn.prepareStatement(sql);
			for (int i = 1; i <= clave.getValores().length; i++) {
				Object valorCol = clave.getValores()[i - 1];
				stmt.setObject(i, valorCol);
			}
			rs = stmt.executeQuery();
			resultado = convertirRS(rs);
		} catch (Exception e) {
			System.out.println("No se pudo realizar la consulta de "
					+ obtenerNombreTabla() + " con la consulta " + sql);
			e.getMessage();
			e.printStackTrace();
		}
		if (resultado.size() == 1) {
			res = resultado.get(0);
		}
		return res;
	}

	public boolean insertar(T entidad) {
		int res = -1;
		boolean resultado = false;
		String sql = null;
		try {
			sql = obtenerInsertStatement(entidad);
			stmt = conn.prepareStatement(sql);
			int count = 1;
			for (Campo campo : obtenerCampos(entidad)) {
				Object obj = campo.getValue();
				if (obj != null) {
					if (obj instanceof Boolean) {
						obj = obj.toString();
					}
					stmt.setObject(count, obj);
					count++;
				}
			}

			res = stmt.executeUpdate();
		} catch (Exception e) {
			System.out.println("Error al insertar " + obtenerNombreTabla() + " con la consulta " + sql);
			System.out.println(e.getMessage());
			e.printStackTrace();
			conn.rollback();
			resultado = false;
		}
		if (res > 0) {
			conn.commit();
			resultado = true;
		} else {
			System.out.println("No se pudo insertar " + obtenerNombreTabla());
			conn.rollback();
			resultado = false;
		}
		return resultado;
	}

	public boolean eliminarPorClave(Clave clave) {
		boolean resultado = false;
		StringBuilder sb = new StringBuilder("DELETE FROM ").append(
				obtenerNombreTabla()).append(" WHERE ");

		for (String nombreCol : clave.getColumnas()) {
			sb.append(nombreCol).append(" = ? AND ");
		}
		sb.delete(sb.length() - 4, sb.length() - 1);

		int res = -1;
		
		String sql = sb.toString();
		
		try {
			stmt = conn.prepareStatement(sql);
			for (int i = 1; i <= clave.getValores().length; i++) {
				Object valorCol = clave.getValores()[i - 1];
				stmt.setObject(i, valorCol);
			}
			res = stmt.executeUpdate();
		} catch (Exception e) {
			System.out.println("Error al eliminar " + obtenerNombreTabla() + "con la consulta " + sql);
			System.out.println(e.getMessage());
			conn.rollback();
			e.printStackTrace();
			resultado = false;
		}

		if (res > 0) {
			conn.commit();
			resultado = true;
		} else {
			System.out.println("No se pudo eliminar " + obtenerNombreTabla());
			conn.rollback();
			resultado = false;
		}
		return resultado;

	}

	public boolean actualizar(T entidad) {
		int res = -1;
		boolean resultado = false;
		String sql = null;
		try {
			sql = obtenerUpdateStatement(entidad);
			stmt = conn.prepareStatement(sql);
			int count = 1;
			for (Campo campo : obtenerCampos(entidad)) {
				Object obj = campo.getValue();
				if (obj != null) {
					if (obj instanceof Boolean) {
						obj = obj.toString();
					}
					stmt.setObject(count, obj);
				} else {
					stmt.setNull(count, getSQLType(campo.getClass()));
				}
				count++;
			}
			for (int i = 1; i <= entidad.getClave().getValores().length; i++) {
				Object valorCol = entidad.getClave().getValores()[i - 1];
				stmt.setObject(count + i - 1, valorCol);
			}

			res = stmt.executeUpdate();
		} catch (Exception e) {
			System.out.println("Error al actualizar " + obtenerNombreTabla() + " con la consulta " + sql);
			System.out.println(e.getMessage());
			e.printStackTrace();
			conn.rollback();
			resultado = false;
		}
		if (res > 0) {
			conn.commit();
			resultado = true;
		} else {
			System.out.println("No se pudo actualizar " + obtenerNombreTabla());
			conn.rollback();
			resultado = false;
		}
		return resultado;
	}

	private int getSQLType(Class<?> clazz) {
		if (clazz.equals(String.class)) {
			return Types.VARCHAR;
		} else if (clazz.equals(Character.class)) {
			return Types.CHAR;
		} else if (clazz.equals(Integer.class)) {
			return Types.INTEGER;
		} else if (clazz.equals(Float.class)) {
			return Types.FLOAT;
		} else if (clazz.equals(Boolean.class)) {
			return Types.BOOLEAN;
		}
		return -1;
	}

}
